﻿#region "Copyright (C) Lenny Granovsky. 2005-2013"
//This program is free software distribute under the terms of the GNU General Public License as published by
//the Free Software Foundation. Please read AssemblyInfo.cs file for more information.
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Web;
using System.Web.UI;
using System.Security.Permissions;
using Web.Enhancements.Rest;

namespace Web.Enhancements.Rest.Help
{
    public class MetadataExplorer
    {
        private WebRestClassConfiguration TypeConfig;
        private static object lockObject;

        static MetadataExplorer()
        {
            lockObject = "";
        }

        public MetadataExplorer()
        {
        }

        [WebRestMethodAttribute(false)]
        [DefaultHttpGet()]
        public string RenderAssemblyInfo(string assemblyName)
        {
            if (!WebRestSettings.Current.AllowExploreMetadata)
                //do not return anything - error 
                throw new HttpException(System.Net.HttpStatusCode.Forbidden.GetHashCode(), ErrorMessages.ResourceManager.GetString("ConfigurationDoesNotAllowExploringServices"));

            Assembly asm = TryAllDomainAssemblies(assemblyName);

            Type[] types = (new List<Type>(asm.GetTypes())).OrderBy(item => item.FullName).ToArray();
            StringBuilder sbr = new StringBuilder("<div>");
            sbr.AppendFormat("<div>Assembly Name: {0}</div>\n", asm.FullName);
            string url = WebRestClient.GetUrlFunctionCall2(typeof(MetadataExplorer), "GetAssemblyClientProxy", "assemblyName=" + assemblyName);
            sbr.AppendFormat("<div>Assembly javascript client proxy: <a href='{0}'>Download</a></div>\n", url);
            sbr.Append("<dl>");
            string assemblyQualifiedName = "";
            foreach (Type type in types)
            {
                if (type.GetInterface("IWebRestService") != null)
                {
                    assemblyQualifiedName = Convert.ToBase64String(System.Text.Encoding.Default.GetBytes(type.AssemblyQualifiedName));
                    url = WebRestClient.GetUrlFunctionCall2(typeof(MetadataExplorer), "RenderClassInfo", string.Format("typeName={0}", assemblyQualifiedName));
                    sbr.Append("<dt>");
                    sbr.AppendFormat("<div><a href='{1}'>{0}</a></div></dt>", type.FullName, url);
                    //add description
                    object[] tmpDsc = type.GetCustomAttributes(typeof(System.ComponentModel.DescriptionAttribute), false);
                    if (tmpDsc.Length > 0)
                        sbr.AppendFormat("<dd class='description'>{0}</dd>", (tmpDsc[0] as System.ComponentModel.DescriptionAttribute).Description);
                }
            }
            sbr.Append("</dl></div>");

            return HelpResources.MetadataHtmlTemplate.Replace("<!--##BODY##-->", sbr.ToString());
        }

        [WebRestMethodAttribute(false, ContentDisposition = "attachment;filename=ClientProxy.js", ContentType = "text/javascript")]
        public string GetAssemblyClientProxy(string assemblyName)
        {
            if (!WebRestSettings.Current.AllowExploreMetadata)
                //do not return anything - error 
                throw new HttpException(System.Net.HttpStatusCode.Forbidden.GetHashCode(), ErrorMessages.ResourceManager.GetString("ConfigurationDoesNotAllowExploringServices"));

            Assembly asm = TryAllDomainAssemblies(assemblyName);

            Type[] types = (new List<Type>(asm.GetTypes())).OrderBy(item => item.FullName).ToArray();
            StringBuilder sbr = new StringBuilder();
            sbr.AppendFormat("//Assembly: {0}\n", asm.FullName);
            sbr.AppendFormat("var webRestSID='{0}';\n", Guid.NewGuid().ToString());
#if DEBUG
            string rsname = "Web.Enhancements.Rest.RestClientJSDebug.js";
#else
            string rsname = "Web.Enhancements.Rest.RestClientJSRelease.js";
#endif
            string script = "";
            using (System.IO.Stream st = this.GetType().Assembly.GetManifestResourceStream(rsname))
            {
                System.IO.StreamReader sr = new System.IO.StreamReader(st, Encoding.Default);
                script = sr.ReadToEnd();
            }
            sbr.Append(script);

            WebRestExplorer expl = new WebRestExplorer();
            foreach (Type type in types)
            {
                if (type.GetInterface("IWebRestService") != null)
                    sbr.Append(expl.BuildClassJSClient(type));
            }
            return sbr.ToString();
        }

        protected Assembly TryAllDomainAssemblies(string assemblyName)
        {
            Assembly[] assemlies = AppDomain.CurrentDomain.GetAssemblies();
            assemblyName += ",";
            foreach (Assembly asm in assemlies)
            {
                if (asm.FullName.StartsWith(assemblyName, StringComparison.OrdinalIgnoreCase))
                    return asm;
            }
            return null;
        }

        [WebRestMethodAttribute(false)]
        public string RenderClassInfo(string typeName)
        {
            if (!WebRestSettings.Current.AllowExploreMetadata)
                //do not return anything - error 
                throw new HttpException(System.Net.HttpStatusCode.Forbidden.GetHashCode(), ErrorMessages.ResourceManager.GetString("ConfigurationDoesNotAllowExploringServices"));

            typeName = System.Text.Encoding.Default.GetString(Convert.FromBase64String(typeName));
            string assemblyName = "";
            string classTypeName = TypeMapper.ExtractFullTypeName(typeName, out assemblyName);
            string key = InfoCaching.JSClassCache.CreateKey(classTypeName);
            //try from cache first
            if (InfoCaching.JSClassCache.Current.ContainsKey(key))
                return InfoCaching.JSClassCache.Current[key];

            //otherwise - create new
            object target = null;
            try
            {
                if (assemblyName.Length == 0)
                    target = Activator.CreateInstance(Type.GetType(classTypeName, true, true));
                else
                {
                    target = Activator.CreateInstance(assemblyName, classTypeName);
                    target = ((System.Runtime.Remoting.ObjectHandle)target).Unwrap();
                }
            }
            catch (Exception e)
            {
                throw new WebRestException(ErrorMessages.ResourceManager.GetString("WebRestExplorerCannotCreateTypeInstance"), typeName, e);
            }
            Type targetType = target.GetType();
            if (targetType.GetInterface("IWebRestService") == null)
                throw new WebRestException(ErrorMessages.ResourceManager.GetString("InvalidClassTypeNoWebRestServiceAttribute"), typeName);
            string tmp = this.BuildClassHtmlInfo(targetType);
            return tmp;
        }

        private string BuildClassHtmlInfo(Type type)
        {
            MethodInfo[] mis = (new List<MethodInfo>(type.GetMethods())).OrderBy(item => item.Name).ToArray();
            StringBuilder sbr = new StringBuilder("<div>");
            this.TypeConfig = WebRestSettings.Current.TypeMappings.Find(type.FullName, type.Assembly.FullName);
            if (this.TypeConfig == null)
                this.TypeConfig = new WebRestClassConfiguration(type.FullName, type.Assembly.FullName);

            if (this.TypeConfig.IncludeNamespaceOnClientSide)
                sbr.AppendFormat("Class: {0}.{1}<br />\n", this.TypeConfig.AliasNamespace, type.Name);
            else
                sbr.AppendFormat("Class (Alias): {0}.<br />\n", this.TypeConfig.AliasName);
            sbr.AppendFormat("Version: '{0}'\n", type.Assembly.GetName().Version.ToString());

            //add filters info
            object[] tmp = type.GetCustomAttributes(typeof(Filters.IWebRestFilterAttribute), false);
            if (tmp.Length > 0)
            {
                foreach (object filter in tmp)
                    sbr.AppendFormat("<div class='details'>Filter: {0}</div>", filter.ToString());
            }

            //add description
            tmp = type.GetCustomAttributes(typeof(System.ComponentModel.DescriptionAttribute), false);
            if (tmp.Length > 0)
                sbr.AppendFormat("<div class='description'>{0}</div>", (tmp[0] as System.ComponentModel.DescriptionAttribute).Description);

            sbr.Append("<ul>");
            foreach (MethodInfo mi in mis)
            {
                tmp = mi.GetCustomAttributes(typeof(WebRestMethodAttribute), false);
                if (tmp.Length > 0)
                    sbr.AppendFormat("{0}\n", BuildMethodHtmlInfo(type, mi, (tmp[0] as WebRestMethodAttribute)));
            }
            sbr.Append("</ul>");
            sbr.AppendFormat("</div>");
            return HelpResources.MetadataHtmlTemplate.Replace("<!--##BODY##-->", sbr.ToString());
        }

        private string BuildMethodHtmlInfo(Type type, MethodInfo mInfo, WebRestMethodAttribute methodAttribute)
        {
            ParameterInfo[] pis = mInfo.GetParameters();
            StringBuilder sbr = new StringBuilder("<li>");

            //add route info
            object[] tmp = mInfo.GetCustomAttributes(typeof(RegisterRouteAttribute), false);
            if (tmp.Length > 0)
            {
                string httpVerbs = (tmp[0] as RegisterRouteAttribute).LimitByHttpMethods;
                if (httpVerbs.Length > 0) httpVerbs = string.Format(" ({0})", httpVerbs);
                string route = (tmp[0] as RegisterRouteAttribute).Route;
                //string codeMethod = (tmp[0] as RegisterRouteAttribute).BindToMethod;
                sbr.AppendFormat("<div class='details'>Route URL{1}: <span class='callUrl'>&lt;root&gt;/{0}</span></div>\n", route, httpVerbs);
            }
            //add route info from class (if any)
            tmp = type.GetCustomAttributes(typeof(RegisterRouteAttribute), false);
            if (tmp.Length > 0)
            {
                foreach (object item in tmp)
                {
                    if ((item as RegisterRouteAttribute).BindToMethod == mInfo.Name)
                    {
                        string httpVerbs = (item as RegisterRouteAttribute).LimitByHttpMethods;
                        if (httpVerbs.Length > 0) httpVerbs = string.Format(" ({0})", httpVerbs);
                        string route = (item as RegisterRouteAttribute).Route;
                        sbr.AppendFormat("<div class='details'>Route URL{1}: <span class='callUrl'>&lt;root&gt;/{0}</span></div>\n", route, httpVerbs);
                    }
                }
            }

            //build URL
            sbr.AppendFormat("<div class='details'>URL: <span class='callUrl'>&lt;root&gt;/{0}.{2}?{1}", this.TypeConfig.AliasName.Replace(".", "/"), mInfo.Name, WebRestSettings.Current.RestFileExtension);
            foreach (ParameterInfo pi in pis)
                sbr.AppendFormat("&{0}=...", pi.Name);
            sbr.Append("</span></div>\n");

            tmp = mInfo.GetCustomAttributes(typeof(DefaultHttpMethodAttribute), false);
            if (tmp.Length > 0)
            {
                string httpVerb = (tmp[0] as DefaultHttpMethodAttribute).HttpMethod;
                //build non-explicit URL
                sbr.AppendFormat("<div class='details'>Non-explicit method URL ({3} only): <span class='callUrl'>&lt;root&gt;/{0}.{2}?", this.TypeConfig.AliasName.Replace(".", "/"), mInfo.Name, WebRestSettings.Current.RestFileExtension, httpVerb);
                foreach (ParameterInfo pi in pis)
                    sbr.AppendFormat("{0}=...&", pi.Name);
                if (sbr[sbr.Length - 1] == '&')
                    sbr.Remove(sbr.Length - 1, 1);
                sbr.Append("</span></div>\n");
                //add Mapping HTTP Method note
                sbr.AppendFormat("<div class='details'>Mapped to HTTP verb: {0}</div>", httpVerb);
            }

            sbr.AppendFormat("<div class='details'>Content Type: {0}</div>", methodAttribute.ContentType);
            sbr.AppendFormat("<div class='details'>Return Object Type: {0}</div>", mInfo.ReturnType.ToString());

            //add cache info
            tmp = mInfo.GetCustomAttributes(typeof(WebRestCacheAttribute), false);
            if (tmp.Length > 0)
            {
                string cacheDescription = (tmp[0] as WebRestCacheAttribute).ToString();
                sbr.AppendFormat("<div class='details'>{0}</div>", cacheDescription);
            }

            //add filters info
            tmp = mInfo.GetCustomAttributes(typeof(Filters.IWebRestFilterAttribute), false);
            if (tmp.Length > 0)
            {
                foreach (object filter in tmp)
                    sbr.AppendFormat("<div class='details'>Filter: {0}</div>", filter.ToString());
            }

            ////build method itself
            //sbr.AppendFormat("{0}.{1}(", this.TypeConfig.AliasName, mInfo.Name);
            //foreach (ParameterInfo pi in pis)
            //    sbr.AppendFormat("{0} {1}, ", pi.ParameterType.Name, pi.Name);
            //if (sbr[sbr.Length - 2] == ',')
            //    sbr.Remove(sbr.Length - 2, 2);
            //sbr.Append(")\n");

            //add description
            tmp = mInfo.GetCustomAttributes(typeof(System.ComponentModel.DescriptionAttribute), false);
            if (tmp.Length > 0)
                sbr.AppendFormat("<div class='description'>Description: {0}</div>", (tmp[0] as System.ComponentModel.DescriptionAttribute).Description);

            //add Obsolete, if any
            tmp = mInfo.GetCustomAttributes(typeof(System.ObsoleteAttribute), false);
            if (tmp.Length > 0)
                sbr.AppendFormat("<div class='obsolete'>Obsolete: {0}</div>", (tmp[0] as System.ObsoleteAttribute).Message.Length > 0 ? (tmp[0] as System.ObsoleteAttribute).Message : "this method is obsolete.");

            //build parameters
            if (pis.Length > 0)
            {
                tmp = mInfo.GetCustomAttributes(typeof(HttpBodyParameterAttribute), false);
                HttpBodyParameterAttribute bodyAttr = null;
                if (tmp.Length > 0)
                    bodyAttr = (tmp[0] as HttpBodyParameterAttribute);
                sbr.AppendFormat("<div class='mparams'><span>Parameters:</span><ul>", this.TypeConfig.AliasName, mInfo.Name);
                foreach (ParameterInfo pi in pis)
                {
                    sbr.AppendFormat("<li>{0} {1} {2}", pi.ParameterType.Name, pi.Name, pi.IsOptional ? " (optional) " : "");
                    if (bodyAttr != null && string.Compare(pi.Name, bodyAttr.ParameterName, true) == 0)
                        sbr.AppendFormat("<span> (from HTTP Request Body as {0})</span>", bodyAttr.ConversionHint.ToString());
                    sbr.Append("</li>");
                }
                sbr.Append("</ul></div>");
            }
            sbr.Append("</li>");
            return sbr.ToString();
        }
    }
}
